﻿using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using UnityEngine;
using System;
using Newtonsoft.Json;
using System.Linq;

namespace Utility.DataSystem
{
	#region interface
	public interface ISettingObject
	{
		bool IsValid { get; }
		string SettingName { get; }
		Type SettingType { get; }
		Setting GetSetting (bool newSetting = false);
	}

	public interface ISettingObject<TSetting> : ISettingObject
		where TSetting : Setting, new()
	{
		new TSetting GetSetting (bool newSetting = false);
	}

	public interface ISettingObjectShowname
	{
		string Showname { get; }
	}
	#endregion
	#region base class	
	public abstract class SettingScriptableObject : ScriptableObject, ISettingObject
	{
		[JsonIgnore]
		protected Setting _setting;
		public Setting GetSetting (bool forceInit = false)
		{
			if (_setting == null)
			{
				_setting = Setting.Create (SettingType, this);
			}
			if (!Application.isPlaying || forceInit)
			{
				InitSetting ();
			}
			return _setting;
		}

		[JsonIgnore]
		public virtual bool IsValid => true;
		[JsonIgnore]
		string ISettingObject.SettingName => name;

		[JsonIgnore]
		public bool enabled = true;
		[JsonIgnore]
		public abstract Type SettingType { get; }

		public void InitSetting ()
		{
			Setting.Init (_setting);
		}

		public virtual void OnCreate () { }
	}

	[Serializable]
	public abstract class SettingObject : ISettingObject
	{
		[JsonIgnore]

		protected Setting _setting;
		public Setting GetSetting (bool forceInit = false)
		{
			if (_setting == null)
			{
				_setting = Setting.Create (SettingType, this);
			}
			if (!Application.isPlaying || forceInit)
			{
				InitSetting ();
			}
			return _setting;
		}

		[JsonIgnore]
		public virtual bool IsValid => true;
		[JsonIgnore]
		public virtual string SettingName => GetType ().Name;
		[JsonIgnore]
		public abstract Type SettingType { get; }

		public void InitSetting ()
		{
			Setting.Init (_setting);
		}
	}

	[JsonConverter (typeof (SettingConverter))]
	public abstract class Setting : IFieldSource
	{
		public static Setting Create (Type type, ISettingObject settingObject)
		{
			var setting = Activator.CreateInstance (type) as Setting;
			setting.SettingObject = settingObject;
			setting.Init ();
			return setting;
		}

		public static void Init (Setting setting)
		{
			setting?.Init ();
		}

		public virtual bool IsValid => SettingObject?.IsValid ?? false;

		protected virtual void Init () { }
		public string SettingName => SettingObject.SettingName;

		protected ISettingObject SettingObject { get; set; }

		#region field system
		string IFieldSource.Name => FieldSourceName;
		protected virtual string FieldSourceName => SettingName;

		protected virtual DataValue GetData (IDataFieldContext context, FieldQuery fieldQuery, out bool isInterested)
		{
			isInterested = false;
			return default;
		}
		DataValue IFieldSource.GetData (IDataFieldContext context, FieldQuery fieldQuery, out bool isInterested) => GetData (context, fieldQuery, out isInterested);
		#endregion
	}


	#endregion
	#region typed class
	public class SettingScriptableObject<TSetting> : SettingScriptableObject, ISettingObject<TSetting>
		where TSetting : Setting, new()
	{
		public override Type SettingType => typeof (TSetting);
		public new TSetting GetSetting (bool forceInit = false) => (TSetting)base.GetSetting (forceInit);
	}

	public class SettingObject<TSetting> : SettingObject, ISettingObject<TSetting>
		where TSetting : Setting, new()
	{
		public override Type SettingType => typeof (TSetting);
		public new TSetting GetSetting (bool forceInit = false) => (TSetting)base.GetSetting (forceInit);
	}

	public class Setting<TSettingObject> : Setting
	{
		protected new TSettingObject SettingObject => (TSettingObject)base.SettingObject;
	}

	#endregion
	#region utility
	public static class SettingObjectUtility
	{
		public static IEnumerable<TSetting> TakeSettings<TSetting> (this IEnumerable<ISettingObject<TSetting>> list, bool forceInit = false, bool keepNull = false)
			where TSetting : Setting, new()
		{
			var objs = (keepNull ? list : list.Where (_NotNull));
			if (forceInit)
			{
				return objs.Select (_TakeSetting_ForceInit);
			}
			else
			{
				return objs.Select (_TakeSetting);
			}

			bool _NotNull (object obj) => obj != null;
			TSetting _TakeSetting (ISettingObject<TSetting> obj) => obj?.GetSetting ();
			TSetting _TakeSetting_ForceInit (ISettingObject<TSetting> obj) => obj?.GetSetting (true);
		}
	}
	#endregion
}